
`timescale 1ns/1ns

module up_wr (

    input                 clk,
    input                 rst_n,

    input       [10:0]    wr_addr_i,
    input       [7:0]     wr_be_i,
    input       [31:0]    wr_data_i,
    input                 wr_en_i,

    output reg  [10:0]    internal_wr_addr_o,
    output reg  [31:0]    internal_wr_data_o,
    output reg            internal_wr_en_o,

    input                 ad_clk,
    input  [31:0]         ad_data_i,
    input                 ad_data_valid_i,
    output  reg           ad_acquisition_en_o, // to AD to start acquisition

    output reg                    up_wr_req_o,
    input                         up_wr_start_i, // acknowledge for up_wr_reg_o
    output [29:0]                 up_pc_mem_addr_o,
    output [31:0]                 up_wr_data_o,
    output reg                    up_wr_data_rdy_o,
    input                         up_wr_done_i

);

localparam       TCQ = 1;  // Clock-to-out delay
localparam       INT_THRESHOLD = 10'd2;   // in units of 128 bytes
/*
* UP_WR_RST
*  initial state, wait for cmd[0] = 1 or cmd[1] = 1. Once cmd[0] = 1 or cmd[1] = 1, reset FIFO
* UP_WR_ENABLE_AD
*  wait three cycles for FIFO ready, turn on AD
* UP_WR_REQ_XFER
*  wait unitl FIFO's prog_empty is 1, turn on up_wr_req_o
* UP_WR_START_XFER
*  wait until up_wr_start_i, if so turn on FIFO->PCIe
* UP_WR_DONE
*  If up_wr_done_i is 1,
    If xfer isn't completed, go to UP_WR_REQ_XFER
    Otherwise go to UP_WR_STAGE_COMPLETED
* UP_WR_STAGE_COMPLETED
*  1) Update xfer length
*  2) Issue MSI
* UP_WR_MSI_DEASSERT
*  If cmd[0] = 1, go to UP_WR_REQ_XFER
*  Otherwise, go to UP_WR_RST
*
*/
localparam       UP_WR_RST             = 3'b000;
localparam       UP_WR_ENABLE_AD       = 3'b001;
localparam       UP_WR_REQ_XFER        = 3'b011;
localparam       UP_WR_START_XFER      = 3'b010;
localparam       UP_WR_DONE            = 3'b110;
localparam       UP_WR_STAGE_COMPLETED = 3'b111;
localparam       UP_WR_MSI_DEASSERT    = 3'b101;

reg [2:0]   up_wr_state;
reg [31:0]  cmd;
reg         cmd_d;
reg [31:0]  xfer_cnt;    // in units of 128 bytes.
reg         fifo_rst;
/*
* the maximum of fifo_rst_cnt depends on the difference between clk and clk_ad
* It should make sure to enable AD module after fifo_rst by THREE cycles.
    */
reg [3:0]   fifo_rst_cnt;
reg         fifo_rd_en;
reg [29:0]  pc_mem_addr_r;
reg [1:0]   pc_mem_index;
reg [29:0]  pc_mem_addr[ 3:0 ];
reg [31:0]  cmd_stub;
wire         fifo_prog_empty;
wire         fifo_prog_full;


assign up_pc_mem_addr_o     = pc_mem_addr_r;

always @(posedge clk) begin
    if ( !rst_n )
        up_wr_data_rdy_o    <= #TCQ 1'b0;
    else
        up_wr_data_rdy_o    <= #TCQ fifo_rd_en;
end
always @(posedge clk) begin
    if ( !rst_n ) begin
        cmd              <= #TCQ 32'd0;
    end else begin
        if ( wr_en_i && ({ wr_addr_i[10:9], wr_addr_i[4], wr_addr_i[1:0]} == 5'b01_0_00 )) begin
            cmd <= #TCQ wr_data_i;
        end else if ( internal_wr_en_o && ({ internal_wr_addr_o[10:9], internal_wr_addr_o[4], internal_wr_addr_o[1:0] } == 5'b01_0_00 ) ) begin
            cmd <= #TCQ internal_wr_data_o;
        end
    end
end
always @(posedge clk) begin
    if ( !rst_n ) begin
        pc_mem_addr[ 0 ] <= #TCQ 30'd0;
        pc_mem_addr[ 1 ] <= #TCQ 30'd0;
        pc_mem_addr[ 2 ] <= #TCQ 30'd0;
        pc_mem_addr[ 3 ] <= #TCQ 30'd0;
    end else if ( wr_en_i && (wr_addr_i[10:9] == 2'b01 )) begin
        case ({ wr_addr_i[4], wr_addr_i[2:1]})
            3'b1_00 : pc_mem_addr[ 0 ] <= #TCQ wr_data_i[31:2];
            3'b1_01 : pc_mem_addr[ 1 ] <= #TCQ wr_data_i[31:2];
            3'b1_10 : pc_mem_addr[ 2 ] <= #TCQ wr_data_i[31:2];
            3'b1_11 : pc_mem_addr[ 3 ] <= #TCQ wr_data_i[31:2];
            default : ;
        endcase
    end
end
always @* begin
    case ({ internal_wr_addr_o[10:9], internal_wr_addr_o[4], internal_wr_addr_o[2:0] } )
        6'b01_0_000 : internal_wr_data_o = cmd_stub;
        6'b01_0_001 : internal_wr_data_o = xfer_cnt;
        6'b01_1_000 : internal_wr_data_o = { pc_mem_addr[ 0 ], 2'b0 };
        6'b01_1_010 : internal_wr_data_o = { pc_mem_addr[ 1 ], 2'b0 };
        6'b01_1_100 : internal_wr_data_o = { pc_mem_addr[ 2 ], 2'b0 };
        6'b01_1_110 : internal_wr_data_o = { pc_mem_addr[ 3 ], 2'b0 };
        default    : internal_wr_data_o = 32'd0;
    endcase
end

always @(posedge clk) begin
    if ( !rst_n ) begin
        cmd_d   <= #TCQ 1'b0;
    end else begin
        cmd_d   <= #TCQ cmd[ 1 ];
    end
end


always @(posedge clk ) begin
    if ( !rst_n ) begin
        up_wr_state         <= #TCQ UP_WR_RST;
        fifo_rst            <= #TCQ 1'b0;
        fifo_rst_cnt        <= #TCQ 4'd0;
        pc_mem_addr_r       <= #TCQ 30'h11220000;
        pc_mem_index        <= #TCQ 2'd0;
        xfer_cnt            <= #TCQ 32'd0;
        fifo_rd_en          <= #TCQ 1'b0;
        ad_acquisition_en_o <= #TCQ 1'b0;
        internal_wr_en_o    <= #TCQ 1'b0;
        internal_wr_addr_o  <= #TCQ 11'b010_0000_0000;
        cmd_stub            <= #TCQ 32'b0;
    end else begin
        case (up_wr_state )
            UP_WR_RST  : begin
                up_wr_req_o <= #TCQ 1'b0;
                fifo_rd_en  <= #TCQ 1'b0;
                ad_acquisition_en_o <= #TCQ 1'b0;
                internal_wr_en_o    <= #TCQ 1'b0;
                if ( cmd[ 0 ] || ( !cmd_d && cmd[ 1 ] ) ) begin
                    xfer_cnt        <= #TCQ 32'd0;
                    internal_wr_addr_o  <= #TCQ 11'b010_0000_0001;
                    internal_wr_en_o    <= #TCQ 1'b1;
                    up_wr_state     <= #TCQ UP_WR_ENABLE_AD;
                    fifo_rst_cnt    <= #TCQ 4'd1;
                    fifo_rst        <= #TCQ 1'b1;
                    pc_mem_addr_r   <= #TCQ pc_mem_addr[ 0 ];//32'h33440000; //pc_mem_addr[ 0 ];
                end else begin
                    up_wr_state     <= #TCQ UP_WR_RST;
                end
            end
            UP_WR_ENABLE_AD : begin
                up_wr_req_o <= #TCQ 1'b0;
                fifo_rst    <= #TCQ 1'b0;
                fifo_rd_en  <= #TCQ 1'b0;
                internal_wr_en_o    <= #TCQ 1'b0;
                if ( fifo_rst_cnt == 4'b00 ) begin
                    up_wr_state <= #TCQ UP_WR_REQ_XFER;
                    ad_acquisition_en_o <= #TCQ 1'b1;
                end else begin
                    fifo_rst_cnt    <= #TCQ fifo_rst_cnt + 4'b1;
                    up_wr_state <= #TCQ UP_WR_ENABLE_AD;
                    ad_acquisition_en_o <= #TCQ 1'b0;
                end
            end
            UP_WR_REQ_XFER  : begin
                fifo_rd_en  <= #TCQ 1'b0;
                if ( !cmd[0] && !cmd[1] ) begin
                    up_wr_req_o <= #TCQ 1'b0;
                    up_wr_state <= #TCQ UP_WR_STAGE_COMPLETED;
                end else begin
                    if ( !fifo_prog_empty ) begin
                        up_wr_req_o <= #TCQ 1'b1;
                        up_wr_state <= #TCQ UP_WR_START_XFER;
                    end else begin
                        up_wr_req_o <= #TCQ 1'b0;
                        up_wr_state <= #TCQ UP_WR_REQ_XFER;
                    end
                end
            end
            UP_WR_START_XFER    : begin
                if ( up_wr_start_i ) begin
                    fifo_rd_en  <= #TCQ 1'b1;
                    up_wr_req_o <= #TCQ 1'b0;
                    up_wr_state <= #TCQ UP_WR_DONE;
                end else begin
                    fifo_rd_en  <= #TCQ 1'b0;
                    up_wr_state <= #TCQ UP_WR_START_XFER;
                end
            end
            UP_WR_DONE  : begin
                if ( up_wr_done_i ) begin
                    fifo_rd_en      <= #TCQ 1'b0;
                    xfer_cnt        <= #TCQ xfer_cnt + 32'd1;
                    if ( xfer_cnt[ 11:0] == 12'hF ) begin
                    //if ( xfer_cnt[ 0 ] ) begin // 2 * 128 bytes.
                        up_wr_state <= #TCQ UP_WR_STAGE_COMPLETED;
                    end else begin
                        up_wr_state <= #TCQ UP_WR_REQ_XFER;
                        pc_mem_addr_r   <= #TCQ pc_mem_addr_r + 30'd32;
                    end
                end else begin
                    fifo_rd_en  <= #TCQ 1'b1;
                    up_wr_state <= #TCQ UP_WR_DONE;
                end
            end
            UP_WR_STAGE_COMPLETED   : begin
                fifo_rd_en          <= #TCQ 1'b0;
                // update XferLen
                internal_wr_addr_o  <= #TCQ 11'b010_0000_0001;
                internal_wr_en_o    <= #TCQ 1'b1;

                pc_mem_index        <= #TCQ pc_mem_index +2'd1;
                // omit to issue MSI
                up_wr_state <= #TCQ UP_WR_MSI_DEASSERT;
                cmd_stub    <= #TCQ cmd;
            end
            UP_WR_MSI_DEASSERT  : begin
                fifo_rd_en  <= #TCQ 1'b0;
                if ( cmd[ 0 ] ) begin
                    pc_mem_addr_r       <= #TCQ pc_mem_addr[ pc_mem_index ];
                    up_wr_state         <= #TCQ UP_WR_REQ_XFER;
                    internal_wr_en_o    <= #TCQ 1'b0;
                end else begin
                    if ( cmd[ 1 ] ) begin
                        internal_wr_addr_o  <= #TCQ 11'b010_0000_0000;
                        cmd_stub[ 1 ]       <= #TCQ 1'b0;
                        internal_wr_en_o    <= #TCQ 1'b1;
                    end
                    up_wr_req_o <= #TCQ 1'b0;
                    up_wr_state <= #TCQ UP_WR_RST;
                end
            end
            default:
                up_wr_state <= #TCQ UP_WR_RST;
        endcase

    end
end

my_fifo_v9_2 AD_FIFO (
    .rst(!rst_n || fifo_rst ),
    .wr_clk( ad_clk ),
    .rd_clk(clk),
    .din( ad_data_i ),
    .wr_en( ad_data_valid_i ),
    .rd_en( fifo_rd_en ),
    .dout( up_wr_data_o ),
    .full(full),
    .empty(empty),
    .prog_empty(fifo_prog_empty)
);

// synthesis translate_off
reg  [8*20:1] up_wr_state_ascii;
always @(up_wr_state ) begin
    case (up_wr_state )
        UP_WR_RST             : up_wr_state_ascii <= #TCQ "RST";
        UP_WR_ENABLE_AD       : up_wr_state_ascii <= #TCQ "EN_AD";
        UP_WR_REQ_XFER        : up_wr_state_ascii <= #TCQ "REQ";
        UP_WR_START_XFER      : up_wr_state_ascii <= #TCQ "START";
        UP_WR_DONE            : up_wr_state_ascii <= #TCQ "DONE";
        UP_WR_STAGE_COMPLETED : up_wr_state_ascii <= #TCQ "STAGE_CPLD";
        UP_WR_MSI_DEASSERT    : up_wr_state_ascii <= #TCQ "MSI_DEAST";
        default               : up_wr_state_ascii <= #TCQ "UNKNOWN_STATE";
    endcase
end
// synthesis translate_on

endmodule

